package mesh

import (
	"cgs-server/helper"
	"cgs-server/internal/cgserver/embedfile"
	"cgs-server/pkg/file"
	"cgs-server/server"
	"cgs-server/server/base"
	"cgs-server/server/model"
	"errors"
	"fmt"
	uuid "github.com/satori/go.uuid"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type MeshService interface {
	UploadMesh(file *ZipFile, user *model.User, isInternal ...bool) (bson.M, error)
	FindMeshById(id string) (*Model, error)
	FindMeshByUrl(url string) (*Model, error)
	Add3DTiles(mdl *model.DataUserModel) error

	// AddDefaultModels 添加系统默认的模型库
	AddDefaultModels(modelPath string) error
}

func NewMeshService() MeshService {
	return &meshService{
		meshRepository: NewMeshRepository(),
	}
}

type meshService struct {
	meshRepository MeshRepository
}

func (m *meshService) AddDefaultModels(modelPath string) error {
	//if modelPath == "" {
	//	modelPath = "./default_models"
	//}
	// 1. 根据名称和类型 和内置模型库类型 查找数据库
	filter := bson.M{
		"isInternal": true,
	}
	many, err := m.meshRepository.FindMany(filter)
	if err != nil {
		return err
	}
	mp := make(map[string]*Model, len(many))
	for _, m2 := range many {
		mp[m2.Name] = m2
	}

	//dir, err := ioutil.ReadDir(modelPath)
	readDir, err := embedfile.DefaultModels.ReadDir("default_models")

	if err != nil {
		return err
	}
	for _, info := range readDir {
		if info.IsDir() {

		} else {
			ext := filepath.Ext(info.Name())
			if ext != ".zip" {
				continue
			}
			name := strings.TrimSuffix(info.Name(), ext)
			if _, ok := mp[name]; ok {
				continue
			}

			//open, err := ioutil.ReadFile(fmt.Sprintf("%s%s%s", modelPath, string(filepath.Separator), info.Name()))
			open, err := embedfile.DefaultModels.ReadFile("default_models/" + info.Name())
			if err != nil {
				continue
			}

			f := ZipFile{
				Filename: info.Name(),
				Size:     int64(len(open)),
				Content:  open,
			}

			_, err = m.UploadMesh(&f, nil, true)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (m *meshService) Add3DTiles(mdl *model.DataUserModel) error {
	mesh, ok := mdl.Data.(*Model)
	if !ok {
		return errors.New("mesh type error")
	}

	pinyin := helper.ConvertToPinYin(mesh.Name)

	mesh.AddTime = time.Now()
	mesh.FirstPinYin = pinyin.FirstPinYin
	mesh.TotalPinYin = pinyin.TotalPinYin
	mesh.Type = "3DTiles"
	mesh.UserId = mdl.User.GetId()

	return m.meshRepository.Insert(mdl)
}

func (m *meshService) UploadMesh(file *ZipFile, user *model.User, isInternal ...bool) (bson.M, error) {
	//file := files["file"][0]
	fileName := file.Filename
	fileSize := file.Size
	fileType := file.FileType
	fileExt := filepath.Ext(fileName)
	fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)
	fileName = fileNameWithoutExt + fileExt

	if strings.ToLower(fileExt) != ".zip" {
		return nil, errors.New("Only zip file is allowed!")
	}

	// save file
	now := time.Now()

	// todo 这个路径可能会重复
	v4, err := uuid.NewV4()
	if err != nil {
		return nil, err
	}
	u1 := v4.String()
	u1 = strings.ReplaceAll(u1, "-", "")
	u1 = u1[:6]
	savePath := fmt.Sprintf("/Upload/Model/%v%v", helper.TimeToString(now, "yyyyMMddHHmmss"), u1)
	physicalPath := server.MapPath(savePath)

	tempPath := filepath.Join(physicalPath, "temp")

	if _, err := os.Stat(tempPath); os.IsNotExist(err) {
		os.MkdirAll(tempPath, 0755)
	}

	targetPath := filepath.Join(tempPath, fileName)
	target, err := os.Create(targetPath)
	if err != nil {
		return nil, err
	}
	defer target.Close()
	target.Write(file.Content)
	//err = ioutil.WriteFile(targetPath, file.Content, 0755)
	//if err != nil {
	//	return nil, err
	//}

	err = helper.UnZip(targetPath, physicalPath)
	if err != nil {
		return nil, err
	}

	//os.RemoveAll(tempPath)

	// justify file type
	entryFileName, meshType, err := getMeshType(savePath, targetPath)
	if err != nil {
		return nil, err
	}

	// save to mongo
	pinyin := helper.ConvertToPinYin(fileNameWithoutExt)

	db, err := server.Mongo()
	if err != nil {
		return nil, err
	}

	doc := bson.M{
		"ID":          primitive.NewObjectID(),
		"AddTime":     now,
		"FileName":    fileName,
		"FileSize":    fileSize,
		"FileType":    fileType,
		"FirstPinYin": pinyin.FirstPinYin,
		"Name":        fileNameWithoutExt,
		"SaveName":    fileName,
		"SavePath":    savePath,
		"Thumbnail":   "",
		"TotalPinYin": pinyin.TotalPinYin,
		"Type":        meshType,
		"Url":         entryFileName,
	}
	//doc["UserID"] = user.UserId.Hex()
	//if server.Config.Authority.Enabled {
	//	user, _ := server.GetCurrentUser(r)
	//
	//}

	if user != nil {
		doc["UserID"] = user.GetId()
	}
	if len(isInternal) > 0 {
		doc["isInternal"] = isInternal[0]
	}

	insertResult, err := db.InsertOne(server.MeshCollectionName, doc)
	if err != nil {
		return nil, err
	}
	doc["_id"] = insertResult.InsertedID
	return doc, nil
}

func (m *meshService) FindMeshById(id string) (*Model, error) {
	objectId, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return nil, err
	}

	return m.meshRepository.FindMeshBy_id(objectId)
}

func (m *meshService) FindMeshByUrl(url string) (*Model, error) {
	return m.meshRepository.FindMeshByUrl(url)
}

func getMeshType(savePath, targetPath string) (entryFileName string, meshType Type, err error) {
	meshType = Unknown
	physicalPath := server.MapPath(savePath)
	infos, err := ioutil.ReadDir(physicalPath)
	if err != nil {
		return
	}

	for _, info := range infos {
		if info.IsDir() {
			continue
		}
		if strings.HasSuffix(strings.ToLower(info.Name()), ".3ds") {
			// Here is url, DO NOT use filepath.Join
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = ThreeDs
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".3mf") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = ThreeMf
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".amf") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Amf
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".assimp") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Assimp
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".bin") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Binary
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = JSON
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".js") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Js
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".awd") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Awd
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".babylon") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Babylon
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".bvh") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Bvh
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".ctm") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Ctm
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".dae") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Dae
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".drc") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Drc
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".fbx") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Fbx
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".gcode") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Gcode
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".glb") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Glb
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".gltf") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Gltf
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".geo") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Geo
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".qgis") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Qgis
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".qgis2_3") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Qgis2_3
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".qgis2_5") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Qgis2_5
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".kmz") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Kmz
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".md2") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Md2
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".nrrd") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Nrrd
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".obj") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Obj
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".pcd") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Pcd
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".pdb") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Pdb
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".ply") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Ply
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".pmd") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Pmd
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".pmx") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Pmx
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".prwm") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Prwm
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".sea") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Sea3d
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".stl") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Stl
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".vrm") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Vrm
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".wrl") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Vrml
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".vtk") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Vtk
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".cgs") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Cgs
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".ts") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Ts
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".dxf") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Dxf
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".x") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = X
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".qgis2web") {
			entryFileName = fmt.Sprintf("%v/%v", savePath, info.Name())
			meshType = Qgis2Web
			break
		} else if strings.HasSuffix(strings.ToLower(info.Name()), ".lmesh") {
			lmesh := info.Name()
			lanim := ""
			ltexture := ""
			for _, n := range infos {
				if strings.HasSuffix(strings.ToLower(n.Name()), ".lanim") {
					lanim = n.Name()
				} else if strings.HasSuffix(strings.ToLower(n.Name()), ".png") {
					ltexture = n.Name()
				}
			}
			if lanim == "" {
				err = errors.New("lanim file is not uploaded!")
				return
			}
			if ltexture == "" {
				err = errors.New("png file is not uploaded!")
				return
			}
			lmesh = fmt.Sprintf("%v/%v", savePath, lmesh)
			lanim = fmt.Sprintf("%v/%v", savePath, lanim)
			ltexture = fmt.Sprintf("%v/%v", savePath, ltexture)
			entryFileName = fmt.Sprintf("%v;%v;%v", lmesh, lanim, ltexture)
			meshType = Lol
			break
		}
	}

	if meshType == Unknown {
		if err = os.RemoveAll(physicalPath); err != nil {
			return
		}
		err = errors.New("Unknown file type!")
		return
	}

	if meshType == Qgis2Web {
		if err = DealQgis2WebFile(savePath, targetPath); err != nil {
			return
		}
	}

	return
}

func DealQgis2WebFile(savePath, targetPath string) error {
	physicalPath := server.MapPath(savePath)
	//physicalPath := savePath
	//savePath = strings.ReplaceAll(savePath, "\\", `/`)
	infos, err := ioutil.ReadDir(physicalPath)
	if err != nil {
		return err
	}

	// 删除除 temp 文件夹外的所有文件
	for _, info := range infos {
		if info.IsDir() {
			if info.Name() == "temp" {
				continue
			}
			if err := os.RemoveAll(filepath.Join(physicalPath, info.Name())); err != nil {
				return err
			}
		} else {
			if err := os.Remove(filepath.Join(physicalPath, info.Name())); err != nil {
				return err
			}
		}
	}

	data, err := ioutil.ReadFile(targetPath)
	if err != nil {
		return err
	}
	zipFile, err := base.ReadZipFile(data)

	jsVariableRe := regexp.MustCompile(`((var|let)\s+\w+\s*=\s*)`)
	// 用来提取 html 的配置信息
	settingRe := regexp.MustCompile(`center:\s*\[(?P<center>.*?)],\s*zoom:\s*(?P<zoom>.*?),\s*bearing:\s*(?P<bearing>.*?),`)

	dataMap := make(map[string]string)

	for _, zf := range zipFile {
		paths := strings.Split(zf.Name, "/")
		name := filepath.Base(zf.Name)
		ext := filepath.Ext(name)
		name = strings.TrimSuffix(name, ext)
		if paths[0] == "data" {
			if ext == ".js" {
				// 提取 js 文件中的 json 数据
				data := jsVariableRe.ReplaceAllString(string(zf.Body), "")
				data = helper.MinifyJson(data)
				filename := name + ".json"
				dataMap["json_"+name] = fmt.Sprintf("%s/%s/%s", savePath, filepath.Dir(zf.Name), filename)
				err = file.WriteFileWithBytes(filepath.Join(physicalPath, filepath.Dir(zf.Name)), filename, []byte(data))
				if err != nil {
					return err
				}
			} else {
				// 其他文件原本保存下来
				err = file.WriteFileWithBytes(filepath.Join(physicalPath, filepath.Dir(zf.Name)), filepath.Base(zf.Name), zf.Body)
				if err != nil {
					return err
				}
			}
		}
	}

	//fmt.Printf("%+v\n", dataMap)

	var styleData string
	var styleFileName string
	var settingMap = make(map[string]interface{})

	for _, zf := range zipFile {
		ext := filepath.Ext(zf.Name)
		if strings.ToLower(ext) == ".qgis2web" {
			styleFileName = zf.Name
			styleData = jsVariableRe.ReplaceAllString(string(zf.Body), "")
		}
		// 处理 html 中的 mapbox 的配置信息
		if zf.Name == "index.html" {
			settingSubmatch := settingRe.FindAllStringSubmatch(string(zf.Body), -1)
			names := settingRe.SubexpNames()
			if len(settingSubmatch[0]) != len(names) {
				continue
			}
			for i, name := range names {
				if i == 0 {
					continue
				}

				value := strings.TrimSpace(settingSubmatch[0][i])

				switch name {
				case "center":
					centerSlice := strings.Split(value, ",")
					if len(centerSlice) == 2 {
						lng, err := strconv.ParseFloat(centerSlice[0], 64)
						if err != nil {
							continue
						}
						lat, err := strconv.ParseFloat(centerSlice[1], 64)
						if err != nil {
							continue
						}
						settingMap[name] = [2]float64{lng, lat}
					}
				case "bearing", "zoom":
					bearing, err := strconv.ParseFloat(value, 64)
					if err != nil {
						continue
					}
					settingMap[name] = bearing
				default:
					settingMap[name] = settingSubmatch[0][i]
				}
			}
		}
	}
	for k, v := range dataMap {
		styleData = strings.Replace(styleData, k, fmt.Sprintf(`"%s"`, v), 1)
	}

	styleData = strings.ReplaceAll(styleData, `'`, `"`)
	styleData = strings.ReplaceAll(styleData, ` `, ``)
	styleData = helper.MinifyJson(styleData)
	styleDataByte := helper.TrimTrailingComma([]byte(styleData))

	// 修改 style 的数据
	styleObj := bson.M{}
	err = helper.FromJSON(styleDataByte, &styleObj)
	if err != nil {
		return err
	}
	for k, v := range settingMap {
		styleObj[k] = v
	}

	styleDataByte, err = helper.ToJSON(styleObj)
	if err != nil {
		return err
	}

	err = file.WriteFileWithBytes(filepath.Join(physicalPath, filepath.Dir(styleFileName)), filepath.Base(styleFileName), styleDataByte)
	if err != nil {
		return err
	}

	return nil
}
